package pl.edu.agh.mr.nb.gui;

import java.util.Properties;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;

import pl.edu.agh.mr.nb.node.ComputationOrderMsg;
import pl.edu.agh.mr.nb.node.ComputationResultMsg;
import pl.edu.agh.mr.nb.node.NodeAction;
import pl.edu.agh.mr.nb.node.ObjMessage;

import cgl.narada.jms.NBJmsInitializer;

import com.trolltech.qt.core.QPoint;
import com.trolltech.qt.core.QSize;
import com.trolltech.qt.gui.*;

public class GUIClient extends QWidget implements MessageListener {

	private static final double minX1 = -2.0;
	private static final double maxX2 =  2.0;
	private static final double minY1 = -2.0;
	private static final double maxY2 = 2.0;
	
    Ui_GUIClient ui = new Ui_GUIClient();
    FractalPainter paintingWidget;
    
    private double x1 = minX1;
    private double x2 = maxX2;
    private double y1 = minY1;
    private double y2 = maxY2;
    private double cx;
    private double cy;

    public static boolean x;
	//TODO: change topic
   // private static final String clientToServerTopic = "nodeToServerTopic";
    private static final String clientToServerTopic = "clientToServerTopic";

	private TopicSession registeringPubSession;
    private TopicSession ordersPubSession;
    private TopicSession resultsSubSession;
    private TopicPublisher registeringPublisher;
    private TopicPublisher ordersPublisher;
    private TopicSubscriber resultsSubscriber;
    private TopicConnection connection;
    private String clientName;
    
	private int id = 0;
	private String nodeToServerTopic = "nodeToServerTopic";
    
    public void zoomIn(QPoint topLeft, QPoint bottomRight) {
    	double dx = (x2 - x1)/FractalPainter.DIMX;
    	double dy = (y2 - y1)/FractalPainter.DIMY;
    	
    	int frameX1 = topLeft.x() < bottomRight.x() ? topLeft.x() : bottomRight.x();
    	int frameX2 = topLeft.x() >= bottomRight.x() ? topLeft.x() : bottomRight.x();
    	int frameY1 = topLeft.y() < bottomRight.y() ? topLeft.y() : bottomRight.y();
    	int frameY2 = topLeft.y() >= bottomRight.y() ? topLeft.y() : bottomRight.y();
    	
    	x2 = x1 + frameX2*dx;
    	x1 = x1 + frameX1*dx;
    	y2 = y1 + frameY2*dy;
    	y1 = y1 + frameY1*dy;
    	
    	sendRequest();
    }
    
    public void zoomOut() {
    	double dx = (x2 - x1)/FractalPainter.DIMX;
    	double dy = (y2 - y1)/FractalPainter.DIMY;

    	x1 -= dx * (FractalPainter.DIMX/2)*5;
    	if (x1<minX1) x1 = minX1;
    	x2 += dx * (FractalPainter.DIMX/2)*5;
    	if (x2>maxX2) x2 = maxX2;
    	
    	y1 -= dy * (FractalPainter.DIMY/2)*5;
    	if (y1<minY1) y1 = minY1;
    	y2 += dy * (FractalPainter.DIMY/2)*5;
    	if (y2>maxY2) y2 = maxY2;
    	
    	sendRequest();
    }
    
    public void reset() {
        x1 = minX1;
        x2 = maxX2;
        y1 = minY1;
        y2 = maxY2;
        reload();
    }
    
    public void reload() {
    	sendRequest();
    }
    
    public void setCX(String x) {
    	cx = Double.valueOf(x);
    }
    
    public void setCY(String y) {
    	cy = Double.valueOf(y);
    }
    
    private void block() {
    	paintingWidget.block();
    	QApplication.invokeLater(new Runnable() {
            public void run() {
            	ui.reloadButton.setEnabled(false);
            	ui.resetButton.setEnabled(false);
            }
        });
    	
    }
    
    private void unblock() {
    	paintingWidget.unblock();

    	QApplication.invokeLater(new Runnable() {
            public void run() {
            	ui.reloadButton.setEnabled(true);
            	ui.resetButton.setEnabled(true);
            }
        });
    }
    
    private void sendRequest() {
    	block();

    	ComputationOrderMsg order = new ComputationOrderMsg(cx,cy,x1,x2,y1,y2,FractalPainter.DIMX,FractalPainter.DIMY);
        order.id = id++;
        
    	try {
	   		
    		sendOrderToServer(order);
    		
	   		System.out.println("Order sent!!!");		
    	} catch (JMSException e) {
    		System.err.println("Cannot send order");
		}
    }
    
    public void showFractal(int[] results) {
    	int colors[][] = new int[FractalPainter.DIMX][FractalPainter.DIMY];
	    for (int i=0;i<FractalPainter.DIMX;i++) {
    		for (int j=0; j<FractalPainter.DIMY;j++) {
				colors[i][j]=results[i*FractalPainter.DIMY+j];
			}
	    }
    	paintingWidget.paintFractal(colors);
    	unblock();
    }

    public static void main(String[] args) {
        
        String transport = "niotcp";
        
        try {
            if (args.length != 3) {
                System.out.println("Usage: java pl.edu.agh.mr.nb.server.GUIClient <hostname> " + "<portNum> <uniqueClientName>");
                System.exit(0);
            }

            Properties props = new Properties();

            /** These properties pertain to setting up a TCP link */
            props.put("hostname", args[0]);
            props.put("portnum", args[1]);

   
            QApplication.initialize(args);

            GUIClient testGUIClient = new GUIClient();
            testGUIClient.show();
            testGUIClient.initializeSession(props, transport, args[2]);
          
            testGUIClient.register();
            
            QApplication.exec();

            //TODO unregistering after pressing zamknij or x             
            //testGUIClient.unregister();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    public GUIClient() {
    	init();
    }

    public GUIClient(QWidget parent) {
        super(parent);
        init();
    }
    
    private void init() {
    	ui.setupUi(this);
    	paintingWidget = new FractalPainter(this);
        
    	paintingWidget.setObjectName("paintingWidget");
        QSizePolicy sizePolicy = new QSizePolicy(com.trolltech.qt.gui.QSizePolicy.Policy.Expanding, com.trolltech.qt.gui.QSizePolicy.Policy.Expanding);
        sizePolicy.setHorizontalStretch((byte)0);
        sizePolicy.setVerticalStretch((byte)0);
        sizePolicy.setHeightForWidth(paintingWidget.sizePolicy().hasHeightForWidth());
        paintingWidget.setSizePolicy(sizePolicy);
        paintingWidget.setMinimumSize(new QSize(600, 600));
        paintingWidget.setMaximumSize(new QSize(600, 600));
        
        ui.horizontalLayout.insertWidget(0, paintingWidget);
        
        ui.reloadButton.clicked.connect(this, "reload()");
        ui.resetButton.clicked.connect(this, "reset()");
        
        ui.cReInput.textChanged.connect(this,"setCX(String)");
        ui.cImInput.textChanged.connect(this,"setCY(String)");
        
        paintingWidget.zoomedIn.connect(this, "zoomIn(QPoint,QPoint)");
        paintingWidget.zoomedOut.connect(this, "zoomOut()");
        
        setCX(ui.cReInput.text());
        setCY(ui.cImInput.text());
        

    }

    // All functions responsible for narada communication 
    private void initializeSession(Properties connectionProperties, String transportType,String clientName) throws Exception {

        NBJmsInitializer ini;
        ini = new NBJmsInitializer(connectionProperties, transportType);

        // Lookup a JMS connection factory
        TopicConnectionFactory conFactory = (TopicConnectionFactory) ini.lookup();
    
        this.clientName = clientName;
    	
        // Create a JMS connection
        TopicConnection connection = conFactory.createTopicConnection("guest", "password");

        this.connection = connection;
        
        // Create a JMS session object
        registeringPubSession = connection.createTopicSession(false,
                Session.AUTO_ACKNOWLEDGE);
        ordersPubSession = connection.createTopicSession(false,
                Session.AUTO_ACKNOWLEDGE);
        resultsSubSession = connection.createTopicSession(false,Session.AUTO_ACKNOWLEDGE);
                
        // Lookup a JMS topic
        Topic registeringTopic = registeringPubSession.createTopic(nodeToServerTopic+"/register");
        Topic resultsTopic = resultsSubSession.createTopic(clientToServerTopic+"/"+clientName);
        Topic ordersTopic = ordersPubSession.createTopic(clientToServerTopic);

        //Topic resultsTopic = resultsSubSession.createTopic("nodeToServerTopic/results");
        //Topic ordersTopic = ordersPubSession.createTopic("nodeToServerTopic/node1");

        // Create a JMS publisher and subscriber
        registeringPublisher = registeringPubSession.createPublisher(registeringTopic);
        ordersPublisher = ordersPubSession.createPublisher(ordersTopic);
        resultsSubscriber = resultsSubSession.createSubscriber(resultsTopic);
        resultsSubscriber.setMessageListener(this);
        
        // Start the JMS connection; allows messages to be delivered
        connection.start();
    }

    public void startConnection() throws JMSException {
        connection.start();
    }

    public void stopConnection() throws JMSException {
        connection.stop();
    }
   
    /* Close the JMS connection */
    public void closeConnection() throws JMSException {
        connection.close();
    }

    
    public void register() throws JMSException {
        TextMessage message = registeringPubSession.createTextMessage();
        message.setText(clientName);
        message.setIntProperty("type", ObjMessage.CLIENT);
        message.setIntProperty("action", NodeAction.REGISTER_NODE); // it should be changed but by now it can work
        message.setStringProperty("clientName", clientName);
        registeringPublisher.publish(message);
    }
    
    public void unregister() throws JMSException {
        TextMessage message = registeringPubSession.createTextMessage();
        message.setText(clientName);
        message.setIntProperty("type", ObjMessage.CLIENT);
        message.setIntProperty("action", NodeAction.UNREGISTER_NODE); // the same as above
        registeringPublisher.publish(message);
    }
    
    
    // sending order message to server
    public void sendOrderToServer(ComputationOrderMsg order) throws JMSException {
    	ObjectMessage message = ordersPubSession.createObjectMessage();
    	order.clientName = clientName;
    	message.setObject(order);
    	message.setIntProperty("type", ObjMessage.CLIENT); 
    	message.setStringProperty("name", clientName);// ok after adding clientName to the object its not important
    	ordersPublisher.publish(message);
    	System.out.println("GUI Klient: Order message sent");
    }

	@Override
	public void onMessage(Message msg) {
		ObjectMessage message = (ObjectMessage) msg;
		ComputationResultMsg result;
		try {
			result = (ComputationResultMsg) message.getObject();
		} catch (JMSException e) {
			System.err.println("Cannot get Result from result message");
			return;
		}
		System.out.println("Got results: "+result.size+" points");
		x = true;
		showFractal(result.results);
	}

	
}
